#include "stdafx.h"
#include "CScrLanguage.h"

COpcode				*	COpcodes::s_pOpcodes[];
int						CCommands::s_nNumCommands;
int						CCommands::s_nNumCustomCommands;
CCommand			*	CCommands::s_pCommands[];
CCommand			*	CCommands::s_pLastFoundCommand;
CList<CCommand>			CCommands::s_pCommandList;

bool CCommands::LoadFromFile(const char *szFilePath)
{
	char *szOpcode, *szName, *szArglist;
	DWORD opcode;

	if(FILE *hFile = OpenFile(szFilePath, "rt"))
	{
		try
		{
			while(char *szStr = GetConfigLine(hFile))
			{
				// [Opcode] [Name]([Arglist])
				szOpcode = strtok(szStr, " \t");
				szName = strtok(NULL, " \t(");
				szArglist = strtok(NULL, ")");

				// Retrieve the opcode number
				if(IsStringHex(szOpcode))
				{
					opcode = StringToHex(szOpcode);

					// Create the command
					auto pCommand = ParseCommandDeclaration(opcode, szName);

					// Fill the command's argument list (params & returns)
					if(szArglist) ParseCommandArgList(pCommand, szArglist);
				}
				else THROW("Invalid command ID '%s'", szOpcode);
			}
			CloseFile(hFile);
			return true;
		}
		catch(const char *e)
		{
			CloseFile(hFile);
			char szError[256];
			szncpy(szError, e, sizeof(szError));
			THROW("'%s' in file '%s'", szError, szFilePath);
		}
	}
	else throw("Failed to open file");
	return false;
}

CCommand * CCommands::ParseCommandDeclaration(DWORD opcode, char *szName)
{
	CCommand *pCommand, *pLastOverloadCommand = nullptr;

	// If this command name is already in use, create the new command as an overload
	if(pCommand = FindFirstNamed(szName))
	{
		// Rewind
		while(pLastOverloadCommand = pCommand->GetPrevOverload())
			pCommand = pLastOverloadCommand;

		// Link the command overloads
		do
		{
			pCommand->IsOverloaded(true);
			pLastOverloadCommand = pCommand;
		}
		while(pCommand = pCommand->GetNextOverload());
	}

	// If * is before the command name, we assume this command is a NOP
	bool bIsNOP = false;
	if(*szName == '*')
	{
		bIsNOP = true;
		++szName;
	}

	// ? as command name specifies an unknown and cant be used without declaration
	// This enforces an in-script declaration for more share-friendly code
	if(szName[0] == '?' && szName[1] == 0)
	{
		sprintf(g_szTempString, "UNKNOWN_%04X", opcode);
		pCommand = Add(opcode, g_szTempString);
		pCommand->SetIsUnknown(true);
	}
	else
	{
		// ? before command name specifies a conditional (can be used with IF)
		if(*szName == '?')
		{
			pCommand = Add((short)opcode, ++szName);
			pCommand->SetIsConditional(true);
		}
		else pCommand = Add((short)opcode, szName);
		
		// Link list the overloaded commands
		if(pLastOverloadCommand)
		{
			pLastOverloadCommand->m_pNextOverload = pCommand;
			pCommand->m_pPrevOverload = pLastOverloadCommand;
			pCommand->IsOverloaded(true);
		}
		
		pCommand->SetIsNOP(bIsNOP);

		// Add command to the searchable binary tree
		s_pCommandList.Add(pCommand->m_dwHash, pCommand);
	}
	return pCommand;
}

char * CCommands::ParseCommandArgList(CCommand *pCommand, char *szStr)
{
	char *szParam = strtok(szStr, ",");

	if(szParam)
	{
		do
		{
			while(IsSpace(*szParam)) szParam++;

			if(*szParam)
			{
				// = before param identifier indicates an out (return) arg
				bool bIsReturn = false;
				if(*szParam == '=')
				{
					++szParam;
					bIsReturn = true;
				}

				// collect flags
				eArgFlag flags = ARGFLAG_NONE;
				while(auto f = CSyntax::GetTypeFlagByIdentifier(*szParam))
				{
					++szParam;
					switch(f)
					{
					case ARGFLAG_OMISSION:
					case ARGFLAG_NUM_EXTRA_PARAMS:
					case ARGFLAG_VAR_EXTRA_PARAMS:
					case ARGFLAG_SIZE_LIMITED:
						if(bIsReturn)
							THROW("Invalid flag for return '%c'", CSyntax::GetIdentifierForTypeFlag(f));
					case ARGFLAG_PARSABLE:
					case ARGFLAG_UNKNOWN_PARAM:
					case ARGFLAG_ANY:
						if((flags & f) != ARGFLAG_NONE)
							THROW("Flag already specified '%c'", CSyntax::GetIdentifierForTypeFlag(f));
						flags |= f;
						break;
					}
				}

				if(bIsReturn) flags |= ARGFLAG_RETURN;

				if(*szParam)
				{
					auto pTypeBase = CSyntax::GetTypeByID(szParam);
					if(!pTypeBase)
						THROW("Could not find a type with the identifier '%s'", szParam);
					auto pArg = pCommand->AddArg(pTypeBase, flags);
				}
			}
		}
		while(szParam = strtok(NULL, ","));
	}

	return szStr;
}